/**
 * Copyright (c) 2021-2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "compiler_logger.h"
#include "optimizer/ir_builder/inst_builder.h"
#include "optimizer/ir_builder/ir_builder.h"
#include "optimizer/ir/inst.h"
#include "bytecode_instruction.h"
#include "bytecode_instruction-inl.h"
#include "include/coretypes/tagged_value.h"

#include "irtoc_builder.cpp"

namespace ark::compiler {

// Currently we support two strategies for building IR from ecma.* instructions:
//   1) Each ecma.* instruction is translated to a corresponding intrinsic call.
//      This is used for bytecode optimizer and slow paths during compiling
//      to native code (in both JIT and AOT modes).
//   2) Semantics of each ecma.* instruction is taken from the corresponding
//      IRtoC handler and is inlined into compiled function.
//      This is used only for native compilation (again, both JIT and AOT).
// InstBuilder::BuildEcma selects proper strategy and calls relevant builder.

// NOLINTNEXTLINE(readability-function-size)
void InstBuilder::BuildEcma([[maybe_unused]] const BytecodeInstruction* bcInst)
{
#ifdef ENABLE_BYTECODE_OPT
    switch (bcInst->GetOpcode()) {
% Panda::instructions.select{|b| b.namespace == "ecmascript"}.each do |inst|
        case BytecodeInstruction::Opcode::<%= inst.opcode.upcase %>: {
%   if inst.compilable? && inst.inlinable?
            // +compilable, +inlinable: ecma.* -> intrinsics for BCO, inline IRtoC otherwise:
            if (GetGraph()->IsBytecodeOptimizer()) {
                BuildEcmaAsIntrinsics(bcInst);
            } else {
                if (g_options.IsCompilerInlineFullIntrinsics()) {
                    BuildEcmaFromIrtoc(bcInst);
                } else {
                    BuildEcmaAsIntrinsics<true>(bcInst);
                }
            }
%   elsif inst.compilable?
            // +compilable, -inlinable: ecma.* -> intrinsics for all scenarios:
            BuildEcmaAsIntrinsics(bcInst);
%   else
%      abort "isa.yaml inconsistency: #{inst.opcode.upcase} is not compilable, but inlinable" if inst.inlinable?
            // -compilable, -inlinable: ecma.* -> intrinsics for BCO, fail IR builder otherwise:
            if (GetGraph()->IsBytecodeOptimizer()) {
                BuildEcmaAsIntrinsics(bcInst);
            } else {
                failed_ = true;
            }
%   end
            break;
        }
% end
        default: {
            failed_ = true;
            LOG(ERROR, COMPILER) << "Unknown ecma.* opcode: " << static_cast<int>(bcInst->GetOpcode());
            return;
        }
    }
#endif
}

template <bool WITH_SPECULATIVE>
void InstBuilder::BuildEcmaAsIntrinsics(const BytecodeInstruction* bcInst) // NOLINT(readability-function-size)
{
    switch (bcInst->GetOpcode()) {
% Panda::instructions.select{|b| b.namespace == "ecmascript"}.each do |inst|
%   opc = inst.opcode.upcase
%   name = opc.split('_')[1]
%     acc_read = inst.acc.include?("in")
%     acc_write = inst.acc.include?("out")
%     ret_type = acc_write ? "compiler::DataType::ANY" : "compiler::DataType::VOID"
%     iname = inst.intrinsic_name ? inst.intrinsic_name : opc
        case BytecodeInstruction::Opcode::<%= opc %>: {
            auto intrinsicId = <%= "compiler::RuntimeInterface::IntrinsicId::" + iname %>;
            auto inst = GetGraph()->CreateInstIntrinsic(<%= ret_type %>, GetPc(bcInst->GetAddress()), intrinsicId);
%     if inst.throwing?
            inst->SetFlag(inst_flags::CAN_THROW);
%     end
%     if inst.exceptions.include?('x_throw') || inst.properties.include?('return')
            inst->SetFlag(inst_flags::CF);
            inst->SetFlag(inst_flags::TERMINATOR);
%     end
%     has_profile = inst.profiled?
%     if has_profile
            [[maybe_unused]] auto profile = GetRuntime()->GetBytecodeProfile(methodProfile_, bcInst->GetAddress(), inst->GetPc());
%     end
%     params_arr = inst.operands
%     format = "BytecodeInstruction::Format::" + inst.format.pretty.upcase
%     range_should_exclude_last = name == "NEWOBJDYNRANGE" || name == "SUPERCALL" || name == "CREATEOBJECTWITHEXCLUDEDKEYS"
%     is_range_call = name == "CALLIRANGEDYN" || name == "CALLITHISRANGEDYN" || range_should_exclude_last
%     need_newtarget = name == "SUPERCALLSPREAD" || name == "SUPERCALL"
%     num_vregs = params_arr.select{|b| b.reg?}.length
%     num_imms = params_arr.select{|b| b.imm?}.length
%     num_ids = params_arr.select{|b| b.id?}.length
%
%     use_this_func = inst.properties.include? 'func'
%     use_cp = inst.properties.include? 'cp'
%     use_lex_env = inst.properties.include? 'lex_env'
%     write_lex_env = inst.properties.include? 'write_lex_env'
%
%     num_inputs = acc_read ? num_vregs + 2 : num_vregs + 1
%     num_inputs += use_this_func ? 1 : 0
%     num_inputs += use_cp ? 1 : 0
%     num_inputs += use_lex_env ? 1 : 0
%     use_ic = inst.properties.include? 'use_ic'
%     if range_should_exclude_last
%       num_inputs = num_inputs - 1
%     end
%     if need_newtarget
%       num_inputs = num_inputs + 1
%     end
%     if is_range_call
            size_t argsCount = <%= num_inputs %>U + static_cast<size_t>(bcInst->GetImm<<%= format %>, 0>());
%     else
            size_t argsCount {<%= num_inputs %>U};
%     end
            if (!inst->RequireState()) {
                --argsCount;
            }
%     if need_newtarget
            if (GetGraph()->IsBytecodeOptimizer()) {
                --argsCount;
            }
%     end
%     if use_ic
%       num_inputs = num_inputs + 1
            if (!GetGraph()->IsBytecodeOptimizer()) {
              argsCount++;  // Instruction supports IC: +1 input for IC slot index
            }
%     end
            inst->ReserveInputs(argsCount);
            inst->AllocateInputTypes(GetGraph()->GetAllocator(), argsCount);
            inst->SetInlined(true);

%     imm_index = 0
%     vreg_index = 0
%     id16_index = 0
%     id32_index = 0
            auto instSaveState = CreateSaveState(Opcode::SaveState, GetPc(bcInst->GetAddress()));
%     if num_ids > 0
%           abort "Too many ids in inputs: #{opc}" if num_ids > 1
            LoadFromPoolDynamic *loadFromPool = nullptr;
            if (!GetGraph()->IsBytecodeOptimizer()) {
                loadFromPool = graph_->CreateInstLoadFromConstantPool(DataType::ANY, inst->GetPc());
                loadFromPool->SetInput(0, GetEnvDefinition(CONST_POOL_IDX));
                instSaveState->AppendBridge(loadFromPool);
                AddInstruction(loadFromPool);
                inst->AppendInput(loadFromPool);
                inst->AddInputType(DataType::ANY);
                inst->SetHasIdInput();
            }
%     end
            AddInstruction(instSaveState);
%     params_arr.each_with_index do |param, input_index|
%       if param.imm?
            auto imm<%= imm_index %> = static_cast<uint32_t>(bcInst->GetImm<<%= format %>, <%= imm_index %>>());
            inst->AddImm(GetGraph()->GetAllocator(), imm<%= imm_index %>);
%           imm_index = imm_index + 1
%       elsif param.reg?
            {
                auto input = GetDefinition(bcInst->GetVReg<<%= format %>, <%= vreg_index %>>());
%         if has_profile && (inst.profile.properties.include?('operand_types_1') || inst.profile.properties.include?('operand_types_2'))
                // NOLINTNEXTLINE(readability-braces-around-statements, bugprone-suspicious-semicolon)
                if constexpr (WITH_SPECULATIVE) {
                    profiling::AnyInputType allowedInputType {};
                    bool isTypeProfiled = false;
                    auto operandType = GetRuntime()->GetProfilingAnyType(profile, bcInst, <%= input_index %>, &allowedInputType, &isTypeProfiled);
                    auto anyCheck = BuildAnyTypeCheckInst(GetPc(bcInst->GetAddress()), input, instSaveState, operandType);
                    InitAnyTypeCheckInst(anyCheck, isTypeProfiled, allowedInputType);
                    input = anyCheck;
                    if (g_options.IsCompilerEcmaReplaceIntrinsicsToDeopt() && !isTypeProfiled) {
                        inst->SetReplaceOnDeoptimize();
                    }
                }
%         end
                inst->AppendInput(input);
                inst->AddInputType(DataType::ANY);
            }
%           vreg_index = vreg_index + 1
%       elsif param.id?
%         abort "Id is not the first input: #{opc}" if input_index > 0
%         if inst.properties.include?("method_id")
            auto mIdx<%= id16_index %> = bcInst->template GetId<<%= format %>>().AsRawValue();
            if (GetGraph()->IsBytecodeOptimizer()) {
                mIdx<%= id16_index %> = GetRuntime()->ResolveMethodIndex(GetGraph()->GetMethod(), mIdx<%= id16_index %>);
                inst->AddImm(GetGraph()->GetAllocator(), mIdx<%= id16_index %>);
            } else {
                loadFromPool->SetTypeId(mIdx<%= id16_index %>);
            }
%           id16_index = id16_index + 1
%         elsif inst.properties.include?("literalarray_id")
            auto literalarrayId<%= id16_index %> = bcInst->template GetId<<%= format %>>().AsIndex();
            if (GetGraph()->IsBytecodeOptimizer()) {
                inst->AddImm(GetGraph()->GetAllocator(), literalarrayId<%= id16_index %>);
            } else {
                loadFromPool->SetTypeId(literalarrayId<%= id16_index %>);
            }
%           id16_index = id16_index + 1
%         elsif inst.properties.include?("string_id")
            auto stringId<%= id32_index %> = bcInst->template GetId<<%= format %>>().AsFileId().GetOffset();
            if (GetGraph()->IsBytecodeOptimizer()) {
                inst->AddImm(GetGraph()->GetAllocator(), stringId<%= id32_index %>);
            } else {
                loadFromPool->SetTypeId(stringId<%= id32_index %>);
            }
%           id32_index = id32_index + 1
%         end
%       end
%     end
%     if need_newtarget
            if (!GetGraph()->IsBytecodeOptimizer()) {
                auto input = GetDefinition(GetGraph()->GetRuntime()->GetMethodRegistersCount(GetMethod()) + 1);
                // NOLINTNEXTLINE(readability-braces-around-statements, bugprone-suspicious-semicolon)
                if constexpr (WITH_SPECULATIVE) {
                    auto anyCheck = BuildAnyTypeCheckInst(GetPc(bcInst->GetAddress()), input, instSaveState);
                    InitAnyTypeCheckInst(anyCheck);
                    input = anyCheck;
                }
                inst->AppendInput(input);
                inst->AddInputType(DataType::ANY);
            }
%     end
%     if is_range_call
%           range_reg_idx = name == "CREATEOBJECTWITHEXCLUDEDKEYS" ? 1 : 0
%           num_actual_vregs = range_should_exclude_last ? "imm0" : "imm0 + 1"
            size_t startReg = bcInst->GetVReg<<%= format %>, <%= range_reg_idx %>>();
            for (uint32_t i = 1; i < <%= num_actual_vregs %>; ++i) {
                auto input = GetDefinition(startReg + i);
                // NOLINTNEXTLINE(readability-braces-around-statements, bugprone-suspicious-semicolon)
                if constexpr (WITH_SPECULATIVE) {
                    auto anyCheck = BuildAnyTypeCheckInst(GetPc(bcInst->GetAddress()), input, instSaveState);
                    InitAnyTypeCheckInst(anyCheck);
                    input = anyCheck;
                }
                inst->AppendInput(input);
                inst->AddInputType(DataType::ANY);
            }
%     end
%     if acc_read
            {
                auto input = GetDefinitionAcc();
%       if has_profile && (inst.profile.properties.include?('operand_types_1') || inst.profile.properties.include?('operand_types_2'))

                // NOLINTNEXTLINE(readability-braces-around-statements, bugprone-suspicious-semicolon)
                if constexpr (WITH_SPECULATIVE) {
                    profiling::AnyInputType allowedInputType {};
                    bool isTypeProfiled = false;
%         idx = inst.profile.properties.include?('operand_types_2') ? 1 : 0
                    auto operandType = GetRuntime()->GetProfilingAnyType(profile, bcInst, <%= idx %>, &allowedInputType, &isTypeProfiled);
                    auto anyCheck = BuildAnyTypeCheckInst(GetPc(bcInst->GetAddress()), input, instSaveState, operandType);
                    InitAnyTypeCheckInst(anyCheck, isTypeProfiled, allowedInputType);
                    input = anyCheck;
                    if (g_options.IsCompilerEcmaReplaceIntrinsicsToDeopt() && !isTypeProfiled) {
                        inst->SetReplaceOnDeoptimize();
                    }
                }
%       end
                inst->AppendInput(input);
                inst->AddInputType(DataType::ANY);
                inst->SetFlag(compiler::inst_flags::ACC_READ);
            }
%     end
%     if use_ic
            if (!GetGraph()->IsBytecodeOptimizer()) {
              inst->AppendInput(FindOrCreate32BitConstant(GetPc(bcInst->GetAddress())));
              inst->AddInputType(DataType::UINT16);
            }
%     end
%     if use_this_func
            if (!GetGraph()->IsBytecodeOptimizer())
            {
              inst->AppendInput(GetDefinition(vregsAndArgsCount_ + 1 + THIS_FUNC_IDX));
              inst->AddInputType(DataType::ANY);
            }
%    end
%     if use_cp
            if (!GetGraph()->IsBytecodeOptimizer())
            {
              inst->AppendInput(GetEnvDefinition(CONST_POOL_IDX));
              inst->AddInputType(DataType::ANY);
            }
%     end
%     if use_lex_env
            if (!GetGraph()->IsBytecodeOptimizer())
            {
              inst->AppendInput(GetEnvDefinition(LEX_ENV_IDX));
              inst->AddInputType(DataType::ANY);
            }
%     end
            if (inst->RequireState()) {
                inst->AppendInput(instSaveState);
                inst->AddInputType(DataType::NO_TYPE);
            }
%     if write_lex_env
            if (!GetGraph()->IsBytecodeOptimizer())
            {
                UpdateDefinitionLexEnv(inst);
            }
%     end
            AddInstruction(inst);
%     if acc_write
            UpdateDefinitionAcc(inst);
            inst->SetFlag(compiler::inst_flags::ACC_WRITE);
%     end
            break;
        }
% end
        default:
            failed_ = true;
            LOG(ERROR,COMPILER) << "unknown Ecma opcode!" << static_cast<int>(bcInst->GetOpcode());
            return;
  }
}

// NOLINTNEXTLINE(readability-function-size)
void InstBuilder::BuildEcmaFromIrtoc([[maybe_unused]] const BytecodeInstruction* bcInst)
{
#ifdef ENABLE_BYTECODE_OPT
    ASSERT(!GetGraph()->IsBytecodeOptimizer());  // Not applicable for optimizing bytecode
    switch (bcInst->GetOpcode()) {
% Panda::instructions.select{|b| b.namespace == "ecmascript"}.each do |inst|
%   format = "BytecodeInstruction::Format::" + inst.format.pretty.upcase
%   vreg_index = 0
%   imm_index = 0
%   id_index = 0
%   inputs = []
%
%   # Corner case, no inlinable IRtoC handler found:
%   unless inst.inlinable?
        case BytecodeInstruction::Opcode::<%= inst.opcode.upcase %>: {
            UNREACHABLE();  // Inlinable IRtoC handler is not implemented yet
            break;
        }
%     next
%   end
%
%   # Generate code for inlining IRtoC handler:
        case BytecodeInstruction::Opcode::<%= inst.opcode.upcase %>: {
%   inst.operands.each do |param|
%     if param.reg?
            auto vreg<%= vreg_index %> = GetDefinition(bcInst->GetVReg<<%= format %>, <%= vreg_index %>>());
%       inputs.push("vreg#{vreg_index}")
%       vreg_index = vreg_index + 1
%     elsif param.imm?
            auto imm<%= imm_index %>Payload = static_cast<uint32_t>(bcInst->GetImm<<%= format %>, <%= imm_index %>>());
            auto imm<%= imm_index %> = GetGraph()->FindOrCreateConstant(imm<%= imm_index %>Payload);
%       inputs.push("imm#{imm_index}")
%       imm_index = imm_index + 1
%     elsif param.id?
            auto id<%= id_index %>Payload = static_cast<uint32_t>(bcInst->template GetId<<%= format %>>().AsIndex());
            auto loadFromPool = graph_->CreateInstLoadFromConstantPool(DataType::ANY, GetPc(bcInst->GetAddress()));
            loadFromPool->SetInput(0, GetEnvDefinition(CONST_POOL_IDX));
            loadFromPool->SetTypeId(id<%= id_index %>Payload);
            additionalDef_ = loadFromPool;
            AddInstruction(loadFromPool);
%       inputs.push("loadFromPool")
%       id_index = id_index + 1
%     elsif param.prof?
%       # Just skip profile id
%     else
%       abort 'Unexpected param type'
%     end
%   end
%   if inst.acc.include?('in')
            auto acc = GetDefinitionAcc();  // According to ecma.* convention, always goes last
%     inputs.push('acc')
%   end
%   if inst.properties.include?('use_ic')
%     inputs.push('GetGraph()->FindOrCreateConstant(GetPc(bcInst->GetAddress()))')
%   end
%   if inst.properties.include?('func')
%     inputs.push('GetEnvDefinition(THIS_FUNC_IDX)')
%   end
%   if inst.properties.include?('cp')
%     inputs.push('GetEnvDefinition(CONST_POOL_IDX)')
%   end
%   if inst.properties.include?('lex_env')
%     inputs.push('GetEnvDefinition(LEX_ENV_IDX)')
%   end

%   builder = 'Build' + inst.opcode.split('_')[0..1].map do |_| _.capitalize end.join()
            [[maybe_unused]] auto inst = <%= builder %>(GetGraph(), this, &currentBb_, <%= inputs.empty? ? '' : inputs.join(', ') + ', ' %>GetPc(bcInst->GetAddress()), visitedBlockMarker_);
%   if id_index > 0
            additionalDef_ = nullptr;
%   end
%   if inst.acc.include?('out')
            UpdateDefinitionAcc(inst);
%   end
            SyncWithGraph();
            break;
        }
% end
        default: {
            failed_ = true;
            LOG(ERROR, COMPILER) << "Unknown ecma.* opcode: " << static_cast<int>(bcInst->GetOpcode());
            return;
        }
    }
#endif
}

}  // namespace ark::compiler
